package com.zbkj.service.manager.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.zbkj.dal.base.constants.OrderConstants;
import com.zbkj.dal.base.enums.*;
import com.zbkj.dal.common.exception.CrmebException;
import com.zbkj.dal.common.utils.CrmebDateUtil;
import com.zbkj.dal.common.utils.CrmebUtil;
import com.zbkj.dal.common.utils.MoneyUtil;
import com.zbkj.dal.dao.*;
import com.zbkj.dal.entity.fund.UserQuotaChangeRecord;
import com.zbkj.dal.entity.merchant.Merchant;
import com.zbkj.dal.entity.qrcode.QrcodeBusiness;
import com.zbkj.dal.entity.system.SystemUserLevel;
import com.zbkj.dal.entity.user.User;
import com.zbkj.dal.entity.user.UserActivityRecord;
import com.zbkj.dal.entity.user.UserQuotaAccount;
import com.zbkj.dal.entity.user.UserToken;
import com.zbkj.service.manager.QuotaManager;
import com.zbkj.service.manager.ShareProfitManager;
import com.zbkj.service.manager.UserManager;
import com.zbkj.service.model.front.vo.fund.UserQuotaVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@Slf4j
@Component
public class QuotaManagerImpl implements QuotaManager {

    @Resource
    private UserDao userDao;

    @Resource
    private UserManager userManager;

    @Resource
    private UserTokenDao userTokenDao;

    @Resource
    private QrcodeBusinessDao qrcodeBusinessDao;

    @Resource
    private ShareProfitManager shareProfitManager;

    @Resource
    private SystemUserLevelDao systemUserLevelDao;

    @Resource
    private UserQuotaAccountDao userQuotaAccountDao;

    @Resource
    private UserActivityRecordDao userActivityRecordDao;

    @Resource
    private UserQuotaChangeRecordDao userQuotaChangeRecordDao;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private MerchantDao merchantDao;

    @Override
    public UserQuotaAccount userQuotaAccount(Integer userId) {
        LambdaQueryWrapper<UserQuotaAccount> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserQuotaAccount::getUserId, userId);
        wrapper.eq(UserQuotaAccount::getDeleted, BooleanEnums.FALSE.getCode());
        wrapper.eq(UserQuotaAccount::getEnable, BooleanEnums.TRUE.getCode());
        UserQuotaAccount userQuotaAccount = userQuotaAccountDao.selectOne(wrapper);
        if (Objects.isNull(userQuotaAccount)) {
            throw new CrmebException("未查询到当前用户的额度账户信息");
        }
        return userQuotaAccount;
    }

    @Override
    public UserQuotaVO userQuota(Integer userId) {
        UserQuotaAccount userQuotaAccount = this.userQuotaAccount(userId);
        SystemUserLevel systemUserLevel = this.currentLevelInfo(userQuotaAccount.getQuotaLevel());
        UserQuotaVO userQuotaVO = new UserQuotaVO();
        userQuotaVO.setLevelId(systemUserLevel.getId());
        userQuotaVO.setMemberLevel(systemUserLevel.getGrade());
        userQuotaVO.setLevelDesc(systemUserLevel.getName());
        userQuotaVO.setTotalQuota(MoneyUtil.prettyFormat(userQuotaAccount.getQuotaTotal()));
        userQuotaVO.setUsedQuota(MoneyUtil.prettyFormat(userQuotaAccount.getQuotaUsed()));
        userQuotaVO.setAvailableQuota(MoneyUtil.prettyFormat(userQuotaAccount.getQuotaBalance()));
        userQuotaVO.setTotalQuotaOriginal(userQuotaAccount.getQuotaTotal());
        userQuotaVO.setUsedQuotaOriginal(userQuotaAccount.getQuotaUsed());
        userQuotaVO.setAvailableQuotaOriginal(userQuotaAccount.getQuotaBalance());
        userQuotaVO.setLimitDown(systemUserLevel.getLimitDown());
        userQuotaVO.setLimitUp(systemUserLevel.getLimitUp());
        userQuotaVO.setBackImage(systemUserLevel.getBackImage());
        return userQuotaVO;
    }

    @Override
    public SystemUserLevel nextLevelInfo(int currentLevel) {
        SystemUserLevel systemUserLevel = currentLevelInfo(currentLevel);
        Integer grade = systemUserLevel.getGrade();
        LambdaQueryWrapper<SystemUserLevel> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SystemUserLevel::getIsDel, BooleanEnums.FALSE.getCode());
        wrapper.eq(SystemUserLevel::getIsShow, BooleanEnums.TRUE.getCode());
        wrapper.eq(SystemUserLevel::getGrade, grade + 1);
        SystemUserLevel nextLevel = systemUserLevelDao.selectOne(wrapper);
        if (Objects.isNull(nextLevel)) {
            LambdaQueryWrapper<SystemUserLevel> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SystemUserLevel::getIsDel, BooleanEnums.FALSE.getCode());
            queryWrapper.eq(SystemUserLevel::getIsShow, BooleanEnums.TRUE.getCode());
            queryWrapper.orderByDesc(SystemUserLevel::getGrade);
            queryWrapper.last("limit 1");
            return systemUserLevelDao.selectOne(queryWrapper);
        }
        return nextLevel;
    }

    public SystemUserLevel level(BigDecimal totalQuota) {
        LambdaQueryWrapper<SystemUserLevel> levelWrapper = new LambdaQueryWrapper<>();
        levelWrapper.le(SystemUserLevel::getLimitDown, totalQuota);
        levelWrapper.eq(SystemUserLevel::getIsShow, BooleanEnums.TRUE.getCode());
        levelWrapper.eq(SystemUserLevel::getIsDel, BooleanEnums.FALSE.getCode());
        levelWrapper.orderByDesc(SystemUserLevel::getExperience);
        levelWrapper.last("limit 1");
        return systemUserLevelDao.selectOne(levelWrapper);
    }

    @Override
    public SystemUserLevel currentLevelInfo(int currentLevel) {
        LambdaQueryWrapper<SystemUserLevel> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SystemUserLevel::getIsDel, BooleanEnums.FALSE.getCode());
        wrapper.eq(SystemUserLevel::getIsShow, BooleanEnums.TRUE.getCode());
        wrapper.eq(SystemUserLevel::getId, currentLevel);
        return systemUserLevelDao.selectOne(wrapper);
    }

    @Override
    public Map<Integer, String> levelTitle(List<Integer> levelIds) {
        if (CollectionUtil.isEmpty(levelIds)) {
            return new HashMap<>();
        }
        LambdaQueryWrapper<SystemUserLevel> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SystemUserLevel::getIsDel, BooleanEnums.FALSE.getCode());
        wrapper.eq(SystemUserLevel::getIsShow, BooleanEnums.TRUE.getCode());
        wrapper.in(SystemUserLevel::getId, levelIds);
        List<SystemUserLevel> systemUserLevels = systemUserLevelDao.selectList(wrapper);
        return systemUserLevels.stream().collect(Collectors.toMap(SystemUserLevel::getId, SystemUserLevel::getName));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addQuota(Integer userId, BigDecimal amount, String bizOrderNo, String changeType, String image, String remark) {
        this.operateQuota(userId, amount, bizOrderNo, changeType, FundDirectionEnums.INCOME.getCode(), image, remark);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void minusQuota(Integer userId, BigDecimal amount, String bizOrderNo, String changeType, String image, String remark) {
        this.operateQuota(userId, amount, bizOrderNo, changeType, FundDirectionEnums.OUTCOME.getCode(), image, remark);
    }

    public void operateQuota(Integer userId, BigDecimal tradeAmount, String bizOrderNo, String changeType, Integer fundDirection, String image, String remark) {
        AtomicBoolean isFirstLevelUp = new AtomicBoolean(false);
        Pair<User, UserQuotaAccount> userUserQuotaAccountPair = this.quotaAccountPair(userId);
        User user = userUserQuotaAccountPair.getLeft();
        UserQuotaAccount userQuotaAccount = userUserQuotaAccountPair.getRight();
        transactionTemplate.execute(e -> {
            if (Objects.equals(fundDirection, FundDirectionEnums.OUTCOME.getCode()) &&
                    userQuotaAccount.getQuotaBalance().subtract(tradeAmount).compareTo(BigDecimal.ZERO) < 0) {
                throw new CrmebException("用户可用额度不足");
            }

            // 添加额度变动记录
            BigDecimal newTotal = Objects.equals(fundDirection, FundDirectionEnums.INCOME.getCode()) ?
                    userQuotaAccount.getQuotaTotal().add(tradeAmount) : userQuotaAccount.getQuotaTotal().subtract(tradeAmount);
            BigDecimal newBalance = Objects.equals(fundDirection, FundDirectionEnums.INCOME.getCode()) ?
                    userQuotaAccount.getQuotaBalance().add(tradeAmount) : userQuotaAccount.getQuotaBalance().subtract(tradeAmount);
            UserQuotaChangeRecord userQuotaChangeRecord = this.generateQuotaRecord(userQuotaAccount, bizOrderNo, image, tradeAmount,
                    changeType, fundDirection, newTotal, newBalance, userQuotaAccount.getQuotaUsed(), remark);
            userQuotaChangeRecordDao.insert(userQuotaChangeRecord);

            SystemUserLevel level = this.level(newTotal);
            SystemUserLevel currentLevel = this.currentLevelInfo(userQuotaAccount.getQuotaLevel());


            Integer alreadyUpgraded = userQuotaAccount.getUpgraded();
            boolean levelUpdate = !Objects.equals(level.getId(), userQuotaAccount.getQuotaLevel());

            LambdaUpdateWrapper<UserQuotaAccount> userQuotaAccountLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
            userQuotaAccountLambdaUpdateWrapper.set(UserQuotaAccount::getQuotaBalance, newBalance);
            userQuotaAccountLambdaUpdateWrapper.set(UserQuotaAccount::getQuotaTotal, newTotal);
            userQuotaAccountLambdaUpdateWrapper.set(UserQuotaAccount::getQuotaLevel, level.getId());
            if (levelUpdate) {
                userQuotaAccountLambdaUpdateWrapper.set(UserQuotaAccount::getUpgraded, BooleanEnums.TRUE.getCode());
            }
            userQuotaAccountLambdaUpdateWrapper.eq(UserQuotaAccount::getId, userQuotaAccount.getId());
            userQuotaAccountDao.update(null, userQuotaAccountLambdaUpdateWrapper);

            // 更新用户表中的额度
            LambdaUpdateWrapper<User> userLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
            userLambdaUpdateWrapper.set(User::getDuesAmount, newBalance);
            userLambdaUpdateWrapper.set(User::getCreditAmount, newBalance);
            userLambdaUpdateWrapper.set(User::getLevel, level.getId());
            userLambdaUpdateWrapper.set(User::getLastDuesTime, CrmebDateUtil.nowDateTime());
            userLambdaUpdateWrapper.set(User::getUpdateTime, CrmebDateUtil.nowDateTime());
            userLambdaUpdateWrapper.eq(User::getId, user.getId());
            userDao.update(null, userLambdaUpdateWrapper);

            if (levelUpdate) {
                boolean levelUp = level.getGrade() > currentLevel.getGrade();
                UserActivityRecord userActivityRecord = new UserActivityRecord();
                userActivityRecord.setUserCode(String.valueOf(userQuotaAccount.getUserId()));
                userActivityRecord.setActivityType(UserActivityTypeEnums.USER_LEVEL_UPGRADED.getCode());
                userActivityRecord.setBizData(userQuotaChangeRecord.getQuotaNo());
                String activityType = levelUp ? "升级" : "降级";
                userActivityRecord.setActivityDigest(String.format("%s为V%s会员", activityType, level.getGrade()));
                userActivityRecord.setCreateTime(LocalDateTime.now());
                userActivityRecord.setUpdateTime(LocalDateTime.now());
                userActivityRecord.setDeleted(BooleanEnums.FALSE.getCode());
                userActivityRecordDao.insert(userActivityRecord);

                isFirstLevelUp.set(BooleanEnums.isFalse(alreadyUpgraded) && levelUp);
            }

            return true;
        });
        if (isFirstLevelUp.get()) {
            CompletableFuture.runAsync(() -> this.bindSpread(userQuotaAccount.getUserId(), bizOrderNo, tradeAmount));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void useQuota(Integer userId, BigDecimal tradeAmount, String bizOrderNo, String changeType, String image, String remark) {
        this.useOperateQuota(userId, tradeAmount, bizOrderNo, changeType, FundDirectionEnums.OUTCOME.getCode(), image, remark);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void quotaReturn(Integer userId, BigDecimal tradeAmount, String bizOrderNo, String changeType, String image, String remark) {
        this.useOperateQuota(userId, tradeAmount, bizOrderNo, changeType, FundDirectionEnums.INCOME.getCode(), image, remark);
    }

    @Transactional(rollbackFor = Exception.class)
    public void useOperateQuota(Integer userId, BigDecimal tradeAmount, String bizOrderNo, String changeType, Integer fundDirection, String image, String remark) {
        Pair<User, UserQuotaAccount> userUserQuotaAccountPair = this.quotaAccountPair(userId);
        User user = userUserQuotaAccountPair.getLeft();
        UserQuotaAccount userQuotaAccount = userUserQuotaAccountPair.getRight();

        // 添加额度变动记录
        BigDecimal newUsed = Objects.equals(fundDirection, FundDirectionEnums.INCOME.getCode()) ?
                userQuotaAccount.getQuotaUsed().subtract(tradeAmount) : userQuotaAccount.getQuotaUsed().add(tradeAmount);
        BigDecimal newBalance = Objects.equals(fundDirection, FundDirectionEnums.INCOME.getCode()) ?
                userQuotaAccount.getQuotaBalance().add(tradeAmount) : userQuotaAccount.getQuotaBalance().subtract(tradeAmount);
        UserQuotaChangeRecord userQuotaChangeRecord = this.generateQuotaRecord(userQuotaAccount, bizOrderNo, image, tradeAmount,
                changeType, fundDirection, userQuotaAccount.getQuotaTotal(), newBalance, newUsed, remark);
        userQuotaChangeRecordDao.insert(userQuotaChangeRecord);

        // 修改额度账户
        userQuotaAccount.setQuotaUsed(newUsed);
        userQuotaAccount.setQuotaBalance(newBalance);
        userQuotaAccount.setUpdateTime(LocalDateTime.now());
        userQuotaAccountDao.updateById(userQuotaAccount);

        // 更新用户表中的额度
        user.setDuesAmount(newBalance);
        user.setCreditAmount(newBalance);
        user.setUpdateTime(CrmebDateUtil.nowDateTime());
        userDao.updateById(user);
    }

    public void bindSpread(Integer userId, String bizOrderNo, BigDecimal amount) {
        transactionTemplate.execute(e -> {
            Pair<User, UserQuotaAccount> userUserQuotaAccountPair = this.quotaAccountPair(userId);
            User user = userUserQuotaAccountPair.getLeft();
           /* LambdaUpdateWrapper<User> userUpdateWrapper = new LambdaUpdateWrapper<>();
            UserQuotaAccount userQuotaAccount = userUserQuotaAccountPair.getRight();
            LambdaUpdateWrapper<UserQuotaAccount> userQuotaAccountLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
            LambdaQueryWrapper<UserToken> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(UserToken::getUid, userId);
            wrapper.eq(UserToken::getIsDel, BooleanEnums.FALSE.getCode());
            UserToken userToken = userTokenDao.selectOne(wrapper);
            UserActivityRecord userActivityRecord = userActivityRecordDao.selectLastByType(userToken.getToken(), UserActivityTypeEnums.USER_SCAN_INVITE_QRCODE.getCode());
            if (ObjectUtil.isNotNull(userActivityRecord)) {
                String scene = userActivityRecord.getBizData();
                LambdaQueryWrapper<QrcodeBusiness> qrcodeWrapper = new LambdaQueryWrapper<>();
                qrcodeWrapper.eq(QrcodeBusiness::getScene, scene);
                qrcodeWrapper.eq(QrcodeBusiness::getDeleted, BooleanEnums.FALSE.getCode());
                QrcodeBusiness qrcodeBusiness = qrcodeBusinessDao.selectOne(qrcodeWrapper);
                if (ObjectUtil.isNotNull(qrcodeBusiness)) {
                    if (StringUtils.equals(qrcodeBusiness.getQrcodeType(), QrcodeTypeEnums.USER_SPREAD.getCode())) {
                        Integer spreadUid = Integer.valueOf(qrcodeBusiness.getOwnerCode());
                        User spreadUser = userDao.selectById(spreadUid);
                        if (Objects.isNull(spreadUser)) {
                            return true;
                        }
                        userUpdateWrapper.set(User::getSpreadUid, spreadUid);
                        userUpdateWrapper.set(User::getSpreadMerchantId, spreadUser.getSpreadMerchantId());
                        userDao.updateSpreadCount(spreadUid);
                        userManager.addRecruitNum(spreadUid);
                    } else if (StringUtils.equals(qrcodeBusiness.getQrcodeType(), QrcodeTypeEnums.MERCHANT_SPREAD.getCode())) {
                        Integer merchantId = Integer.valueOf(qrcodeBusiness.getOwnerCode());
                        Merchant merchant = merchantDao.selectById(merchantId);
                        if (Objects.nonNull(merchant)) {
                            userUpdateWrapper.set(User::getSpreadMerchantId, merchantId);
                            // 推荐人为当前店铺的店长
                            User spreadUser = userDao.selectById(merchant.getManagerUid());
                            if (Objects.nonNull(spreadUser)) {
                                userUpdateWrapper.set(User::getSpreadUid, spreadUser.getId());
                                userDao.updateSpreadCount(spreadUser.getId());
                                userManager.addRecruitNum(spreadUser.getId());
                            }
                        }
                    }
                }
            }
            userQuotaAccountLambdaUpdateWrapper.set(UserQuotaAccount::getUpgraded, BooleanEnums.TRUE.getCode());
            userUpdateWrapper.set(User::getSpreadTime, CrmebDateUtil.nowDateTime());
            userUpdateWrapper.set(User::getUpdateTime, CrmebDateUtil.nowDateTime());
            userUpdateWrapper.set(User::getEnableShareProfit, BooleanEnums.TRUE.getCode());
            userUpdateWrapper.eq(User::getId, user.getId());
            userDao.update(null, userUpdateWrapper);
            userQuotaAccountLambdaUpdateWrapper.eq(UserQuotaAccount::getId, userQuotaAccount.getId());
            userQuotaAccountDao.update(null, userQuotaAccountLambdaUpdateWrapper);*/

            shareProfitManager.shareProfitInitAndConfirm(user.getId(), bizOrderNo, user.getAvatar(), amount, amount, ShareProfitSceneEnums.RECRUIT);
            return true;
        });
    }

    private UserQuotaChangeRecord generateQuotaRecord(UserQuotaAccount userQuotaAccount, String bizOrderNo, String image,
                                                      BigDecimal tradeAmount, String changeType, Integer fundDirection,
                                                      BigDecimal newTotal, BigDecimal newBalance, BigDecimal newUsed, String remark) {
        UserQuotaChangeRecord userQuotaChangeRecord = new UserQuotaChangeRecord();
        userQuotaChangeRecord.setUserId(userQuotaAccount.getUserId());
        userQuotaChangeRecord.setQuotaNo(CrmebUtil.getOrderNo(OrderConstants.QUOTA_CHANGE_RECORD_NO_PREFIX));
        userQuotaChangeRecord.setImage(image);
        userQuotaChangeRecord.setBizOrderNo(bizOrderNo);
        userQuotaChangeRecord.setQuotaId(userQuotaAccount.getId());
        userQuotaChangeRecord.setQuotaAccount(userQuotaAccount.getQuotaAccount());
        userQuotaChangeRecord.setQuotaChangeAmount(tradeAmount);
        userQuotaChangeRecord.setChangeDirection(fundDirection);
        userQuotaChangeRecord.setChangeType(changeType);
        userQuotaChangeRecord.setOldQuotaTotal(userQuotaAccount.getQuotaTotal());
        userQuotaChangeRecord.setNewQuotaTotal(newTotal);
        userQuotaChangeRecord.setOldQuotaBalance(userQuotaAccount.getQuotaBalance());
        userQuotaChangeRecord.setNewQuotaBalance(newBalance);
        userQuotaChangeRecord.setOldQuotaUsed(userQuotaAccount.getQuotaUsed());
        userQuotaChangeRecord.setNewQuotaUsed(newUsed);
        userQuotaChangeRecord.setStatus(QuotaChangeStatusEnums.EFFICACY.getCode());
        userQuotaChangeRecord.setBillDate(LocalDate.now());
        userQuotaChangeRecord.setCreateTime(LocalDateTime.now());
        userQuotaChangeRecord.setUpdateTime(LocalDateTime.now());
        userQuotaChangeRecord.setDeleted(BooleanEnums.FALSE.getCode());
        userQuotaChangeRecord.setRemark(remark);
        userQuotaChangeRecord.setSignature(DigestUtils.md5DigestAsHex(JSONUtil.toJsonStr(userQuotaChangeRecord).getBytes(StandardCharsets.UTF_8)));
        return userQuotaChangeRecord;
    }

    private Pair<User, UserQuotaAccount> quotaAccountPair(Integer userId) {
        User user = userManager.availableUser(userId);
        UserQuotaAccount userQuotaAccount = this.userQuotaAccount(userId);

        if (userQuotaAccount.getQuotaTotal().compareTo(userQuotaAccount.getQuotaBalance().add(userQuotaAccount.getQuotaUsed())) != 0) {
            log.error("额度账户{}信息异常,total:{},balance:{},used:{}", userQuotaAccount.getQuotaAccount(), userQuotaAccount.getQuotaTotal(), userQuotaAccount.getQuotaBalance(), userQuotaAccount.getQuotaUsed());
            throw new CrmebException("额度账户信息异常");
        }
        if (userQuotaAccount.getQuotaBalance().compareTo(user.getCreditAmount()) != 0) {
            log.error("userId:{}额度账户信息异常,balance:{},creditAmount:{}", user.getId(), userQuotaAccount.getQuotaBalance(), user.getCreditAmount());
            throw new CrmebException("额度账户信息异常");
        }
        return Pair.of(user, userQuotaAccount);
    }

    @Override
    public UserQuotaChangeRecord userQuotaChangeRecord(String quotaNo) {
        LambdaQueryWrapper<UserQuotaChangeRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserQuotaChangeRecord::getQuotaNo, quotaNo);
        wrapper.eq(UserQuotaChangeRecord::getDeleted, BooleanEnums.FALSE.getCode());
        return userQuotaChangeRecordDao.selectOne(wrapper);
    }
}
